} else {
console.log('Command not recognized');
}
The next thing you need to do is add an else if clause that checks whether the
command equals remove. In the else if, I'll open and close my condition and hit enter
just as I did in the previous else if clause; this time, I'll add if the command equals
remove, we want to remove the note. And in that case, all we'll do is to use
console.log to print Reading note, as shown in the following code:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const notes = require('./notes.js');
var command = process.argv[2];
console.log('Command: ', command);
if (command === 'add') {
console.log('Adding new note');
} else if (command === 'list') {
console.log('Listing all notes');
} else if (command === 'read') {
console.log('Reading note');
} else {
console.log('Command not recognized');
}
And with this in place, we are done. If we refer to the code block, we've added
two new commands we can run over in the Terminal, and we can test those:
if (command === 'add') {
console.log('Adding new note');
} else if (command === 'list') {
console.log('Listing all notes');
} else if (command === 'read') {
console.log('Reading note');
} else {
console.log('Command not recognized');
}
First up, I'll run node app.js with the read command, and Reading note shows up:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const notes = require('./notes.js');
var command = process.argv[2];
console.log('Command: ', command);
if (command === 'add') {
console.log('Adding new note');
} else if (command === 'list') {
console.log('Listing all notes');
} else if (command === 'read') {
console.log('Reading note');
} else if (command == 'remove') {
console.log('Removing note');
} else {
console.log('Command not recognized');
}
Then I'll rerun the command; this time, I'll be using remove. And when I do that,
Removing note prints to the screen, as shown in this screenshot:
I'll wrap up my testing using a command that doesn't exist, and when I run that,
you can see Command not recognized shows up.
Getting the specific note
information
Now, what we did in the previous subsection is step 1. We now have support for
various commands. The next thing we need to figure out is how we'll get more
specific information. For example, which note do you want to remove? Which
note do you want to read? And what do you want the note text to be in the case
of adding a note? This is all information we need to get from the Terminal.
Now, getting it is going to be pretty similar to what we did earlier, and to show
you what it looks like, we'll print the entire argv object once again, using the
following command:
console.log(process.argv);
Over in the Terminal, we can now run a more complex command. Let's say we
want to remove a note using the node app.js remove command, and we'll do that by
its title. We might use the title argument, which looks like the following code:
node app.js remove --title
In this title argument, we have -- (two) hyphens followed by the argument
name, which is title, followed by the = (equals) sign. Then we can type our note
title. Maybe the note title is secrets. This will pass the title argument into our
application.
Now, there are a couple of different ways you could format the title argument,
which are as follows:
You could have the title secrets like the one in the preceding command
You could have title equals secrets inside quotes, which will let us use
spaces in the title:
node app.js remove --title=secrets
You can remove the = (equals) sign altogether and simply put a space:
node app.js remove --title="secrets 2"
No matter how you choose to format your argument, these are all valid ways to
pass in the title.
As you see in the preceding screenshot, I am using double quotes
when wrapping my string. Now, if you switch to single quotes, it
will not break on Linux or OS X, but it will break on Windows. That
means when you're passing in command-line arguments such as the
title or the note body, you'll want to wrap your strings, when you
have spaces, in double quotes, not single. So, if you are using
Windows and you're getting some sort of unexpected behavior with
your arguments, make sure you're using double quotes instead of
single; that should fix the issue.
For the moment, I'll keep the = (equals) sign and the quotes and rerun the
command:
node app.js remove --title="secrets 2"
When I run the command, you can see in the following code output that we have
our two arguments:
These are the arguments that we don't need, then we have our remove command,
which is the third one, and we now have a new fourth string, the title that is
equal to secrets 2. And our argument was successfully passed into the
application. The problem is that it's not very easy to use. In the fourth string, we
have to parse out the key, which is title, and the value, which is secrets 2.
When we used the command, which was the third argument in the previous
section, it was a lot easier to use inside our app. We simply pulled it out of the
arguments array and we referenced it by using the command variable and
checking whether it equaled add, list, read, or remove.
Things get a lot more complex as we use different styles for passing in the
arguments. If we rerun the last command with a space instead of an = (equals)
sign, as shown in the following code, which is perfectly valid, our arguments
array now looks completely different:
In the preceding code output, you can see that we have the title as the fourth
item, and we have the value, which is secrets 2, as the fifth, which means we
have to add other conditions for parsing. And this turns into a pain really
quickly, which is why we will not do it.
We'll use a third-party module called yargs in the next chapter to make parsing
the command-line arguments effortless. Instead of having strings, as shown in
this one or the one we discussed earlier, we'll get an object where the title
property equals the secrets 2 string. That will make it super easy to implement the
rest of the notes application.
Now, parsing certain types of command-line arguments, such as key value pairs,
becomes a lot more complex, which is why, in the next chapter, we'll be using
yargs to do just that.
Summary
In this chapter, we learned how to use require to load in modules that come with
Node.js. We created our files for our notes application and required them inside
app.js. We explored how to use built-in modules and we explored how to use
modules we defined. We found out how to require other files that we created,
and how to export things such as properties and functions from those files.
We explored npm a little bit, how we can use npm init to generate a package.json
file, and how we can install and use third-party modules. Next, we explored the
nodemon module, using it to automatically restart our app as we make changes to a
file. Last, we learned how to get input from the user, which is needed to create
the notes application. We learned that we can use command-line arguments to
pass data into our app.
In the next chapter, we'll explore some more interesting Node fundamental
concepts, including yargs, JSON, and Refactor.
Node Fundamentals – Part 2
In this chapter, we'll continue our discussion on some more node fundamentals.
We'll explore yargs, and we'll see how to parse command-line arguments using
process.argv and yargs. After that, we'll explore JSON. JSON is nothing more than
a string that looks kind of like a JavaScript object, with the notable differences
being that it uses double quotes instead of single quotes and all of your property
names—like name and age, in this case—require quotes around them. We'll look
into how to convert an object into a string, then define that string, use it, and
convert it back to an object.
After we've done that, we'll fill out the addNote function. Finally, we'll look into
refactor, moving the functionality into individual functions and testing the
functionality.
More specifically, we'll go through following topics:
yargs
JSON
Adding note
Refactor
yargs
In this section, we will use yargs, a third-party npm module, to make the process
of parsing much easier. It will let us access things such as title and body
information without needing to write a manual parser. This is a great example of
when you should look for an npm module. If we don't use a module, it would be
more productive for our Node application to use a third-party module that has
been tested and thoroughly vetted.
To get started, we'll install the module, then we'll add it into the project, parsing
for things such as a title of the body, and we'll call all the functions that will get
defined over in notes.js. If the command is add, we'll call add note, so on.
Installing yargs
Now, let's view the documents page for yargs. It's always a good idea to know
what you're getting yourself into. If you search for yargs on Google, you should
find the GitHub page as your first search result. As shown in the following
screenshot, we have the GitHub page for the yargs library:
Now, yargs is a very complex library. It has a ton of features for validating all
sorts of input, and it has different ways in which you can format that input. We
will start with a very basic example, although we will be introducing more
complex examples throughout this chapter.
If you want to look at any other features that we don't discuss in the
chapter, or you just want to see how something works that we have
talked about, you can always find it in the yarg documents.
We'll now move into Terminal to install this module inside of our application. To
do this, we'll use npm install followed by the module name, yargs, and in this case,
I'll use the @ sign to specify the specific version of the module I want to use,
11.0.0, which is the most recent version at the time of writing. Next, I'll add the
save flag, which, as we know, updates the package.json file:
npm install yargs@11.0.0 --save
If I leave off the save flag, yargs will get installed into the
node_modules folder, but if we wipe that node_modules folder later and
run npm install, yargs won't get reinstalled because it's not listed in
the package.json file. This is why we use the save flag.
Running yargs
Now that we've installed yargs, we can move over into Atom, inside of app.js,
and get started with using it. The basics of yargs, the very core of its feature set,
is really simple to take advantage of. The first thing we'll do is to require it up, as
we did with fs and lodash in the previous chapter. Let's make a constant and call it
yargs, setting it equal to require('yargs'), as shown here:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const yargs = require('yargs');
const notes = require('./notes.js');
var command = process.argv[2];
console.log('Command:', command);
console.log(process.argv);
if (command === 'add') {
console.log('Adding new note');
} else if (command === 'list') {
console.log('Listing all notes');
} else if (command === 'read') {
console.log('Reading note');
} else if (command === 'remove') {
console.log('Removing note');
} else {
console.log('Command not recognized');
}
From here, we can fetch the arguments as yargs parses them. It will take the
same process.argv array that we discussed in the previous chapter, but it goes
behind the scenes and parses it, giving us something that's much more useful
than what Node gives us. Just above the command variable, we can make a const
variable called argv, setting it equal to yargs.argv, as shown here:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const yargs = require('yargs');
const notes = require('./notes.js');
const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command);
console.log(process.argv);
if (command === 'add') {
console.log('Adding new note');
} else if (command === 'list') {
console.log('Listing all notes');
} else if (command === 'read') {
console.log('Reading note');
} else if (command === 'remove') {
console.log('Removing note');
} else {
console.log('Command not recognized');
}
The yargs.argv module is where the yargs library stores its version of the
arguments that your app ran with. Now we can print it using console.log, and this
will let us take a look at the process.argv and yargs.argv variables; we can also
compare them and see how yargs differs. For the command where we use
console.log to print process.argv, I'll make the first argument a string called Process
so that we can differentiate it in Terminal. We'll call console.log again. The first
argument will be the Yargs string, and the second one will be the actual argv
variable, which comes from yargs:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const yargs = require('yargs');
const notes = require('./notes.js');
const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command);
console.log('Process', process.argv);
console.log('Yargs', argv);
if (command === 'add') {
console.log('Adding new note');
} else if (command === 'list') {
console.log('Listing all notes');
} else if (command === 'read') {
console.log('Reading note');
} else if (command === 'remove') {
console.log('Removing note');
} else {
console.log('Command not recognized');
}
Now we can run our app (refer to the preceding code block) a few different ways
and see how these two console.log statements differ.
First up, we'll run at node app.js with the add command, and we can run this very
basic example:
node app.js add
We already know what the process.argv array looks like from the previous chapter.
The useful information is the third string inside of the array, which is 'add'. In the
fourth string, Yargs gives us an object that looks very different:
As shown in the preceding code output, first we have the underscore property,
then commands such as add are stored.
If I were to add another command, say add, and then I were to add a modifier, say
encrypted, you would see that add would be the first argument and encrypted the
second, as shown here:
node app.js add encrypted
So far, yargs really isn't shining. This isn't much more useful than what we have
in the previous example. Where it really shines is when we start passing in key-
value pairs, such as the title example we used in the Getting input section of
Node Fundamentals - Part 1 in chapter 2. I can set my title flag equal to secrets,
press enter, and this time around, we get something much more useful:
node app.js add --title=secrets
In the following code output, we have the third string that we would need to
parse in order to fetch the value and the key, and in the fourth string, we actually
have a title property with a value of secrets:
Also, yargs has built-in parsing for all the different ways you could specify this.
We can insert a space after title, and it will still work just as it did before; we
can add quotes around secrets, or add other words, like secrets from Andrew, and it
will still parses it correctly, setting the title property to the secrets from Andrew
string, as shown here:
node app.js add --title "secrets from Andrew"
This is where yargs really shines! It makes the process of parsing your
arguments a lot easier. This means that inside our app, we can take advantage of
that parsing and call the proper functions.
Working with the add command
Let's work with the add command, for example, for parsing your arguments and
calling the functions. Once the add command gets called, we want to call a
function defined in notes, which will be responsible for actually adding the note.
The notes.addNote function will get the job done. Now, what do we want to pass to
the addNote function? We want to pass in two things: the title, which is accessible
on argv.title, as we saw in the preceding example; and the body, argv.body:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const yargs = require('yargs');
const notes = require('./notes.js');
const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command);
console.log('Process', process.argv);
console.log('Yargs', argv);
if (command === 'add') {
console.log('Adding new note');
notes.addNote(argv.title, argv.body);
} else if (command === 'list') {
console.log('Listing all notes');
} else if (command === 'read') {
console.log('Reading note');
} else if (command === 'remove') {
console.log('Removing note');
} else {
console.log('Command not recognized');
}
Currently, these command-line arguments, title and body, aren't
required. So technically, the user could run the application without
one of them, which would cause it to crash, but in future, we'll be
requiring both of these.
Now that we have notes.addNote in place, we can remove our console.log statement,
which was just a placeholder, and we can move into the notes application
notes.js.
Inside notes.js, we'll get started by making a variable with the same name as the
method we used over app.js and addNote, and we will set it equal to an anonymous
arrow function, as shown here:
var addNote = () => {
};
Now, this alone isn't too useful, because we're not exporting the addNote function.
Below the variable, we can define module.exports in a slightly different way. In
previous sections, we added properties onto exports to export them. We can
actually define an entire object that gets set to exports, and in this case, we can set
addNote equal to the addNote function defined in preceding code block:
module.exports = {
addNote: addNote
};
In ES6, there's actually a shortcut for this. When you're setting an
object attribute and a value that's a variable and they're both
exactly the same, you can actually leave off the colon and the
value. Either way, the result identical.
In the preceding code, we're setting an object equal to module.exports, and that
object has a property, addNote, which points to the addNote function we defined as a
variable in the preceding code block.
Once again, addNote: and addNote are identical inside of ES6. We will be using the
ES6 syntax for everything throughout this book.
Now I can take my two arguments, title and body, and actually do something with
them. In this case, we'll call console.log and Adding note, passing in the two
arguments as the second and third argument to console.log, title and body, as
shown here:
var addNote = (title, body) => {
console.log('Adding note', title, body);
};
Now we're in a pretty good position to run the add command with title and body
and see if we get exactly what we'd expect, which is the console.log statement
shown in the preceding code to print.
Over in Terminal, we can start by running the app with node app.js, and then
specify the filename. We'll use the add command; which will run the appropriate
function. Then, we'll pass in title, setting it equal to secret, and then we can pass
in body, which will be our second command-line argument, setting that equal to
the string, This is my secret:
node app.js add --title=secret --body="This is my secret"
In this command, we specified three things: the add command the title argument,
which gets set to secret; and the body argument, which gets set to "This is my
secret". If all goes well, we'll get the appropriate log. Let's run the command.
In the following command output, you can see Adding note secret, which is the
title; and This is my secret, which is the body:
With this in place, we now have one of our methods set up and ready to go. The
next thing that we'll do is convert the other commands we have—the list, read,
and remove commands. Let's look into one more command, and then you'll do the
other two by yourself as exercises.
Working with the list command
Now, with the list command, I'll remove the console.log statement and call
notes.getAll, as shown here:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const yargs = require('yargs');
const notes = require('./notes.js');
const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command);
console.log('Process', process.argv);
console.log('Yargs', argv);
if (command === 'add') {
notes.addNote(argv.title, argv.body);
} else if (command === 'list') {
notes.getAll();
} else if (command === 'read') {
console.log('Reading note');
} else if (command === 'remove') {
console.log('Removing note');
} else {
console.log('Command not recognized');
}
At some point, notes.getAll will return all of the notes. Now, getAll doesn't take
any arguments since it will return all of the notes regardless of the title. The read
command will require a title, and remove will also require the title of the note you
want to remove.
For now, we can create the getAll function. Inside notes.js, we'll go through that
process again. We'll start by making a variable, calling it getAll, and setting it
equal to an arrow function, which we've used before. We start with our
arguments list, then we set up the arrow (=>), which is the equal sign and the
greater than sign. Next, we specify the statements we want to run. Inside our
code block, we'll run console.log(Getting all notes), as shown here:
var getAll = () => {
console.log('Getting all notes');
};
The last step to the process after adding that semicolon will be to add getAll to
the exports, as shown in the following code block:
module.exports = {
addNote,
getAll
};
Remember that in ES6, if you have a property whose name is
identical to the value, which is a variable, you can simply remove
the value variable and the colon.
Now that we have getAll in notes.js in place, and we've wired it up in app.js, we
can run things over in Terminal. In this case, we'll run the list command:
node app.js list
In the preceding code output, you can see at the bottom that Getting all notes
prints to the screen. Now that we have this in place, we can remove
console.log('Process', process.argv) from the command variable in app.js. The resultant
code will look like the following code block:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const yargs = require('yargs');
const notes = require('./notes.js');
const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command);
console.log('Yargs', argv);
if (command === 'add') {
notes.addNote(argv.title, argv.body);
} else if (command === 'list') {
notes.getAll();
} else if (command === 'read') {
console.log('Reading note');
} else if (command === 'remove') {
console.log('Removing note');
} else {
console.log('Command not recognized');
}
We will keep the yargs log around since we'll be exploring the other ways and
methods to use yargs throughout the chapter.
Now that we have the list command in place, next, I'd like you to create a
method for the read and remove commands.
The read command
When the read command is used, we want to call notes.getNote, passing in title.
Now, title will get passed in and parsed using yargs, which means that we can
use argv.title to fetch it. And that's all we have to do when it comes to calling the
function:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const yargs = require('yargs');
const notes = require('./notes.js');
const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command);
console.log('Yargs', argv);
if (command === 'add') {
notes.addNote(argv.title, argv.body);
} else if (command === 'list') {
notes.getAll();
} else if (command === 'read') {
notes.getNote(argv.title);
} else if (command === 'remove') {
console.log('Removing note');
} else {
console.log('Command not recognized');
}
The next step is to define getNote, because currently it doesn't exist. Over in
notes.js, right below the getAll variable, we can make a variable called getNote,
which will be a function. We'll use the arrow function, and it will take an
argument; it will take the note title. The getNote function takes the title, then it
returns the body for that note:
var getNote = (title) => {
};
Inside getNote, we can use console.log to print something like Getting note, followed
by the title of the note you will fetch, which will be the second argument to
console.log:
var getNote = (title) => {
console.log('Getting note', title);
};
This is the first command, and we can now test it before we go on to the second
one, which is remove.
Over in Terminal, we can use node app.js to run the file. We'll be using the new
read command, passing in a title flag. I'll use a different syntax, where title gets
set equal to the value outside of quotes. I'll use something like accounts:
node app.js read --title accounts
This accounts value will read the accounts note in the future, and it will print it to
the screen, as shown here:
As you can see in the preceding code output, we get an error, which we'll debug
now.
Dealing with the errors in parsing
commands
Getting an error is not the end of the world. Getting an error usually means that
you have a small typo or you forgot one step in the process. So, we'll first figure
out how to parse through these error messages, because the error messages you
get in the code output can be pretty daunting. Let's refer to the code output error
here:
As you can see, the first line shows you where the error occurred. It's inside of
our app.js file, and the number 19 after the colon is the line number. It shows you
exactly where things went bad. The TypeError: notes.getNote is not a function line is
telling you pretty clearly that the getNote function you tried to run doesn't exist.
Now we can take this information and debug our app.
In app.js, we see that we call notes.getNote. Everything looks great, but when we
move into notes.js, we realize that we never actually exported getNote. This is why
when we try to call the function, we get getNote is not a function. All we have to
do to fix that error message is export getNote, as shown here:
module.exports = {
addNote,
getAll,
getNote
};
Now when we save the file and rerun the app from Terminal, we'll get what we
expect—Getting note followed by the title, which is accounts, as shown here:
This is how we can debug our error messages. Error messages contain really
useful information. For the most part, the first couple of lines are code that
you've written, and the other ones are internal Node code or third-party modules.
In our case, the first line of the stack trace is important, as it shows exactly
where the error occurred.
The remove command
Now, since the read command is working, we can move on to the last one, which
is the remove command. Here, I'll call notes.removeNote, passing in the title, which as
we know is available in argv.title:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const yargs = require('yargs');
const notes = require('./notes.js');
const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command);
console.log('Yargs', argv);
if (command === 'add') {
notes.addNote(argv.title, argv.body);
} else if (command === 'list') {
notes.getAll();
} else if (command === 'read') {
notes.getNote(argv.title);
} else if (command === 'remove') {
notes.removeNote(argv.title);
} else {
console.log('Command not recognized');
}
Next up, we'll define the removeNote function over inside of our notes API file,
right below the getNote variable:
var removeNote = (title) => {
console.log('Removing note', title);
};
Now, removeNote will work much the same way as getNote. All it needs is the title; it
can use this information to find the note and remove it from the database. This
will be an arrow function that takes the title argument.
In this case, we'll print the console.log statement, Removing note; then, as the second
argument, we'll simply print title back to the screen to make sure that it's going
through the process successfully. This time around, we'll export our removeNote
function; we'll define it using the ES6 syntax:
module.exports = {
addNote,
getAll,
getNote,
removeNote
};
The last thing to do is test it and make sure it works. We can reload the last
command using the up arrow key. We change read to remove, and that is all we
need to do. We're still passing in the title argument, which is great, because that
is what remove needs:
node app.js remove --title accounts
When I run this command, we get exactly what we expected. Removing note
prints to the screen, as shown in the following code output, and then we get the
title of the note that we're supposed to be removing, which is accounts:
This looks great! That is all it takes to use yargs to parse your arguments.
With this, we now have a place to define all of that functionality, for saving,
reading, listing, and removing notes.
Fetching command
The last thing I want to discuss before we wrap up this section is—how we fetch
command.
As we know, command is available in the _ property as the first and only item. This
means that in the app.js, var command statement, we can set command equal to argv,
then ._, and then we'll use [] to grab the first item in the array, as shown in the
following code:
console.log('Starting app.js');
const fs = require('fs');
const _ = require('lodash');
const yargs = require('yargs');
const notes = require('./notes.js');
const argv = yargs.argv;
var command = argv._[0];
console.log('Command:', command);
console.log('Yargs', argv);
if (command === 'add') {
notes.addNote(argv.title, argv.body);
} else if (command === 'list') {
notes.getAll();
} else if (command === 'read') {
notes.getNote(argv.title);
} else if (command === 'remove') {
notes.removeNote(argv.title);
} else {
console.log('Command not recognized');
}
With this in place, we now have the same functionality, but we'll use yargs
everywhere. If I rerun the last command, we can test that the functionality still
works. And it does! As shown in the following command output, we can see that
Command: remove shows up:
Next, we'll look into filling out the individual functions. We'll take a look first at
how we can use JSON to store our notes inside our file system.
JSON
Now that you know how to parse command-line arguments using process.argv and
yargs, you've solved the first piece to the puzzle for the notes application. Now,
how do we get that unique input from the user? The second piece to the puzzle is
to solve how we store this information.
When someone adds a new note, we want to save it somewhere, preferably on
the filesystem. So the next time they try to fetch, remove, or read that note, they
actually get the note back. To do this, we'll need to introduce something called
JSON. If you're already familiar with JSON, you probably know it is super
popular. It stands for JavaScript Object Notation, and it's a way to represent
JavaScript arrays and objects using a string. Now, why would you ever want to
do that?
Well, you might want to do that because strings are just text, and that's pretty
much supported anywhere. I can save JSON to a text file, and then I can read it
later, parse it back into a JavaScript array or object, and do something with it.
This is exactly what we'll take a look at in this section.
To explore JSON and how it works, let's go ahead and make a new folder inside
our project called playground.
Throughout the book, I'll create the playground folders and various
projects, which store simple one-off files that aren't a part of the
bigger application; they're just a way to explore a new feature or
learn a new concept.
In the playground folder, we'll make a file called json.js, this is where we can
explore how JSON works. To get started, let's make a very simple object.
Converting objects into strings
Let's first make a variable called obj, setting it equal to an object. On this object,
we'll just define one property, name, and set it equal to your first name; I'll set this
one equal to Andrew, as shown here:
var obj = {
name: 'Andrew'
};
Now, let's assume that we want to take this object and work on it. Let's say we
want to, for example, send it between servers as a string and save it to a text file.
To do this, we'll need to call one JSON method.
Let's take a moment to define a variable to store the result, stringObj, and we'll set
it equal to JSON.stringify, as shown here:
var stringObj = JSON.stringify(obj);
The JSON.stringify method takes your object, in this case, the obj variable, and
returns the JSON-stringified version. This means that the result stored in stringObj
is actually a string. It's no longer an object, and we can take a look at that using
console.log. I'll use console.log twice. First up, we'll use the typeof operator to print
the type of the string object to make sure that it actually is a string. Since typeof is
an operator, it gets typed in lowercase, there is no camel casing. Then, you pass
in the variable whose type you want to check. Next up, we can use console.log to
print the contents of the string itself, printing out the stringObj variable, as shown
here:
console.log(typeof stringObj);
console.log(stringObj);
What we've done here is we've taken an object, converted it into a JSON string,
and printed it onto the screen. Over in Terminal, I'll navigate into the playground
folder using the following command:
cd playground
For now, it doesn't matter where you run the command, but in
future it will matter when we are in the playground folder, so take a
moment to navigate into it.
We can now use node to run our json.js file. When we run the file, we see two
things:
As shown in the preceding code output, first, we will get our type, which is a
string, and this is great, because remember, JSON is a string. Next, we will get
our object, which looks pretty similar to a JavaScript object, but there are a few
differences. These differences are as follows:
First up, your JSON will have its attribute names automatically wrapped in
double quotes. This is a requirement of the JSON syntax.
Next up, you'll notice your strings are also wrapped in double quotes as
opposed to single quotes.
Now, JSON doesn't just support string values, you can use an array, a Boolean, a
number, or anything else. All of those types are perfectly valid inside of your
JSON. In this case, we have a very simple example where we have a name
property and it's set to "Andrew".
This is the process of taking an object and converting it into a string. Next up,
we'll define a string and convert that into an object we can actually use in our
app.
Defining a string and using in app
as an object
Let's get started by making a variable called personString, and we'll to set it equal
to a string using single quotes since JSON uses double quotes inside of itself, as
shown here:
var personString = '';
Then we'll define our JSON in the quotes. We'll start by opening and closing
some curly braces. We'll use double quotes to create our first attribute, which
we'll call name, and we'll set that attribute equal to Andrew. This means that after the
closing quote, we'll add :; then we'll open and close double quotes again and
type the value Andrew, as shown here:
var personString = '{"name": "Andrew"}';
Next up, we can add another property. After the value, Andrew, I'll create another
property after the comma, called age, which will be set equal to a number. I can
use my colon and then define the number without the quotes, in this case, 25:
var personString = '{"name": "Andrew","age": 25}';
You can go ahead and use your name and your age, obviously, but make sure the
rest looks identical to what you see here.
Now, let's say we get the earlier-defined JSON from a server or we grab it from a
text file. Currently, it's useless; if we want to get the name value, there is no good
way to do that because we're using a string, so personString.name doesn't exist.
What we need to do is take the string and convert it back into an object.
Converting a string back to an
object
To convert the string back to object, we'll use the opposite of JSON.stringify,
which is JSON.parse. Let's make a variable to store the result. I'll create a person
variable and it will be set equal to JSON.parse, passing in as the one and only
argument the string you want to parse, in this case, the person string, which we
defined earlier:
var person = JSON.parse(personString);
Now, this variable takes your JSON and converts it from a string back into its
original form, which could be an array or an object. In our case, it converts it
back into an object, and we have the person variable as an object, as shown in the
preceding code. Also, we can prove that it's an object using the typeof operator.
I'll use console.log twice, just like we did previously.
First up, we'll print typeof person, and then we'll print the actual person variable,
console.log(person):
console.log(typeof person);
console.log(person);
With this in place, we can now rerun the command in Terminal; I'll actually start
nodemon and pass in json.js:
nodemon json.js
As shown in the following code output, you can now see that we're working with
an object, which is great, and we have our regular object:
We know that Andrew is an object because it's not wrapped in double quotes; the
values don't have any quotes, and we use single quotes for Andrew, which is valid
in JavaScript, but it's not valid in JSON.
This is the entire process of taking an object, converting it to a string, and then
taking the string and converting it back into the object, and this is exactly what
we'll do in the notes app. The only difference is that we'll be taking the following
string and storing it in a file, then later on, we'll be reading that string from the
file using JSON.parse to convert it back to an object, as shown in the following
code block:
// var obj = {
// name: 'Andrew'
// };
// var stringObj = JSON.stringify(obj);
// console.log(typeof stringObj);
// console.log(stringObj);
var personString = '{"name": "Andrew","age": 25}';
var person = JSON.parse{personString};
console.log(typeof person);
console.log(person);
Storing the string in a file
With the basics in place, let's take it just one step further, that is, by storing the
string in a file. Then, we want to read the contents of that file back by using the
fs module and printing some properties from it. This means that we'll need to
convert the string that we get back from fs.readfilesync into an object using
JSON.parse.
Writing the file in the playground
folder
Let's go ahead and comment out all the code we have so far and start with a
clean slate. First up, let's go ahead and load in the fs module. The const variable
fs will be set equal to require, and we'll pass the fs module that we've used in the
past, as shown here:
// var obj = {
// name: 'Andrew'
// };
// var stringObj = JSON.stringify(obj);
// console.log(typeof stringObj);
// console.log(stringObj);
// var personString = '{"name": "Andrew","age": 25}';
// var person = JSON.parse(personString);
// console.log(typeof person);
// console.log(person);
const fs = require('fs');
The next thing we'll do is define the object. This object will be stored inside of
our file, and then will be read back and parsed. This object will be a variable
called originalNote, and we'll call it originalNote because later on, we'll load it back
in and call that variable Note.
Now, originalNote will be a regular JavaScript object with two properties. We'll
have the title property, which we'll set equal to Some title, and the body property,
which we will set equal to Some body, as shown here:
var originalNote = {
title: 'Some title',
body: 'Some body'
};
The next step that you will need to do is take the original note and create a
variable called originalNoteString, and set that variable equal to the JSON value of
the object we defined earlier. This means that you'll need to use one of the two
JSON methods we used previously in this section.
Now, once you have that originalNoteString variable, we can write a file to the
filesystem. I'll write that line for you, fs.writeFileSync. The writeFileSync method,
which we used before, takes two arguments. One will be the filename, and since
we're using JSON, it's important to use the JSON file extension. I'll call this file
notes.json. The other arguments will be text content, originalNoteString, which is
not yet defined, as shown in this code block:
// originalNoteString
fs.writeFileSync('notes.json', originalNoteString);
This is the first step to the process; this is how we'll write that file into the
playground folder. The next step to the process will be to read out the contents,
parse it using the JSON method earlier, and print one of the properties to the
screen to make sure that it's an object. In this case, we'll print the title.
Reading out the content in the file
The first step to print the title is to use a method we haven't used yet. We'll use
the read method available on the filesystem module to read the contents. Let's
make a variable called noteString. The noteString variable will be set equal to
fs.readFileSync.
Now, readFileSync is similar to writeFileSync except that it doesn't take the text
content, since it's getting the text content back for you. In this case, we'll just
specify the first argument, which is the filename, notes.JSON:
var noteString = fs.readFileSync('notes.json');
Now that we have the string, it will be your job to take that string, use one of the
preceding methods, and convert it back into an object. You can call that variable
note. Next up, the only thing left to do is to test whether things are working as
expected, by printing with the help of console.log(typeof note). Then, below this,
we'll use console.log to print the title, note.title:
// note
console.log(typeof note);
console.log(note.title);
Now, over in Terminal, you can see (refer to the following screenshot) that I
have saved the file in a broken state and it crashed, and that's expected when
you're using nodemon:
To resolve this, the first thing I'll do is fill out the originalNoteString variable,
which we had commented out earlier. It will now be a variable called
originalNoteString, and we'll set it equal to the return value from JSON.stringify.
Now, we know JSON.stringify takes our regular object and it converts the object
into a string. In this case, we'll take the originalNote object and convert it into a
string. The next line, which we already have filled out, will save that JSON
value into the notes.JSON file. Then we will read that value out:
var originalNoteString = JSON.stringify(originalNote);
The next step will be to create the note variable. The note variable will be set
equal to JSON.parse.
The JSON.parse method takes the string JSON and converts it back into a regular
JavaScript object or array, depending on whatever you save. Here we will pass in
noteString, which we'll get from the file:
var note = JSON.parse(noteString);
With this in place, we are now done. When I save this file, nodemon will
automatically restart and we would expect to not see an error. Instead, we expect
that we'll see the object type as well as the note title. Right inside Terminal, we
have object and Some title printing to the screen:
With this in place, we've successfully completed the challenge. This is exactly
how we will save our notes.
When someone adds a new note, we'll use the following code to save it:
var originalNote = {
title: 'Some title',
body: 'Some body'
};
var originalNoteString = JSON.stringify(originalNote);
fs.writeFileSync('notes.json', originalNoteString);
When someone wants to read their note, we'll use the following code to read it:
var noteString = fs.readFileSync('notes.json');
var note = JSON.parse(noteString);
console.log(typeof note);
console.log(note.title);
Now, what if someone wants to add a note? This will require us to first read all
of the notes, then modify the notes array, and then use the code (refer to the
previous code block) to save the new array back into the filesystem.
If you open up that notes.JSON file, you can see right here that we have our JSON
code inside the file:
.json is actually a file format that's supported by most text editors, so I actually
already have some nice syntax highlighting built in. Now, in the next section,
we'll be filling out the addNote function using the exact same logic that we just
used inside of this section.
Adding and saving notes
In the previous section, you learned how to work with JSON inside Node.js, and
this is the exact format we'll be using for the notes.js application. When you first
run a command, we'll load in all the notes that might already exist. Then we'll
run the command, whether it's adding, removing, or reading notes. Finally, if
we've updated the array, like we will when we add and remove notes, we'll save
those new notes back into the JSON file.
Now, this will all happen inside of the addNote function, which we defined in the
notes.js application, and we already wired up this function. In earlier sections, we
ran the app add command, and this function executed with the title and body
arguments.
Adding notes
To get started with adding notes, the first thing we'll do is create a variable called
notes, and for the moment, we'll set it equal to an empty array, just as in the
following, using our square brackets:
var addNote = (title, body) => {
var notes = [];
};
Now that we have the empty array, we can go ahead and make a variable called
note, which is the individual note. This will represent the new note:
var addNote = (title, body) => {
var notes = [];
var note = {
}
};
On that note, we'll have the two properties: a title and a body. Now, title can be
set equal to the title variable, but, as we know, inside ES6, we can simply
remove it when both values are the same; so we'll add title and body as shown
here:
var addNote = (title, body) => {
var notes = [];
var note = {
title,
body
};
};
Now we have the note and the notes array.
Adding notes to the notes array
The next step in the process of adding notes will be to add the note to the notes
array. The notes.push method will let us do just that. The push method on an array
lets you pass in an item, which gets added to the end of the array, and in this
case, we'll pass in the note object. So we have an empty array, and we add our
one item, as shown in the following code; next, we push it in, which means that
we have an array with one item:
var addNote = (title, body) => {
var notes = [];
var note = {
title,
body
};
notes.push(note);
};
The next step in the process will be to update the file. Now, we don't have a file
in place, but we can load an fs function and start creating the file.
Up above the addNote function, let's load in the fs module. I'll create a const
variable called fs and set it equal to the return result from require, and we'll
require the fs module, which is a core node module, so there's no need to install
it using NPM:
const fs = require('fs');
With this in place, we can take advantage of fs inside the addNote function.
Right after we push our item on to the notes array, we'll call fs.writeFileSync,
which we've used before. We know we need to pass in two things: the file name
and the content we want to save. For the file, I'll call, notes-data.JSON, and then
we'll pass in the content to save, which in this case will be the stringify notes
array, which means we can call JSON.stringify passing in notes:
notes.push(note);
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
We could have broken JSON.stringfy(notes) out into its own variable
and referenced the variable in the above statement, but since we'll
only be using it in one place, I find this is the better solution.
At this point, when we add a new note, it will update the notes-data.JSON file,
which will be created on the machine since it does not exist, and the note will sit
inside it. Now, it's important to note that currently every time you add a new
note, it will wipe all existing ones because we never load in the existing ones,
but we can get started testing that this note works as expected.
I'll save the file, and over inside of Terminal, we can run this file using node
app.js. Since we want to add a note, we will be using that add command which we
set up, then we'll specify our title and our body. The title flag can get set equal
to secret, and for the body flag, I'll set it equal to the Some body here string, as shown
here:
node app.js add --title=secret --body="Some body here"
Now, when we run this command from Terminal, we'll see what we'd expect:
As shown in the preceding screenshot, we see a couple of the file commands we
added: we see that the add command was executed, and we have our Yargs
arguments. The title and body arguments also show up. Inside Atom, we also see
that we have a new notes-data.json file, and in the following screenshot, we have
our note, with the secret title and the Some body here body:
This is the first step in wiring up that addNote function. We have an existing notes
file and we do want to take advantage of these notes. If notes already exist, we
don't want to simply wipe them every time someone adds a new note. This
means that in notes.js, earlier at the beginning of the addNote function, we'll fetch
those notes.
Fetching new notes
I'll add code for fetching new notes where I define the notes and note variables.
As shown in the following code, we'll use fs.readFileSync, which we've already
explored. This will take the filename, in our case, notes-data.JSON. Now, we will
want to store the return value from readFileSync on a variable; I'll call that
variable, notesString:
var notesString = fs.readFileSync('notes-data.json');
Since this is the string version, we haven't passed it through the JSON.parse
method. So, I can set notes (the variable we defined earlier in addNote function)
equal to the return value from the JSON.parse method. Then JSON.parse will take the
string from the file we read and it will parse it into an array; we could pass in
notesString just like this:
notes = JSON.parse(notesString);
With this in place, adding a new note is no longer going to remove all of the
notes that were already there.
Over in Terminal, I'll use the up arrow key to load in the last command, and I'll
navigate over to the title flag and change it to secret2 and rerun the command:
node app.js add --title=secret2 --body="Some body here"
In Atom, this time you can see we now have two notes inside of our file:
We have an array with two objects; the first one has the title of secret and the
second one has the title of secret2, which is brilliant!
Trying and catching code block
Now, if the notes-data.json file does not exist, which it won't when the user first
runs the command, the program will crash, as shown in the following code
output. We can prove this by simply rerunning the last command after deleting
the note-data.JSON file:
Right here, you can see we're actually getting a JavaScript error, no such file or
directory; it's trying to open up the notes-data.JSON file, but without much success.
To fix this, we'll use a try-catch statement from JavaScript, which hopefully
you've seen in the past. To brush up this, let's go over it really quick.
To create a try-catch statement, all you do is you type try, which is a reserved
keyword, and then you open and close a set of curly braces. Inside the curly
braces is the code that will run. This is the code that may or may not throw an
error. Next, you'll specify the catch block. Now, the catch block will take an
argument, an error argument, and it also has a code block that runs:
try{
} catch (e) {
}
This code will run if and only if one of your errors in try actually occurs. So, if
we load the file using readFileSync and the file exists, that's fine, catch block will
never run. If it fails, catch block will run and we can do something to recover
from that error. With this in place, all we'll do is move the noteString variable and
the JSON.parse statements into try, as shown here:
try{
var notesString = fs.readFileSync('notes-data.json');
notes = JSON.parse(notesString);
} catch (e) {
}
That's it; nothing else needs to happen. We don't need to put any code in catch,
although you do need to define the catch block. Now, let's take a look at what
happens when we run the whole code.
The first thing that happens is that we create our static variables—nothing
special there—then we try to load in the file. If the notesString function fails, that
is fine because we already defined notes to be an empty array. If the file doesn't
exist and it fails, then we probably want an empty array for notes anyways,
because clearly there are no notes, and there's no file.
Next up, we'll parse that data into notes. There is a chance that this will fail if
there's invalid data in the notes-data.JSON file, so the two lines can have problems.
By putting them in try-catch, we're basically guaranteeing that the program isn't
going to work unexpectedly, whether the file does or doesn't exist, but it contains
corrupted data.
With this in place, we can now save notes and rerun that previous command.
Note that I do not have the notes-data file in place. When I run the command, we
don't see any errors, everything seems to run as expected:
When you now visit Atom, you can see that the notes-data file does indeed exist,
and the data inside it looks great:
This is all we need to do to fetch the notes, update the notes with the new note,
and finally save the notes to the screen.
Now, there is still a slight problem with addNote. Currently, addNote allows for
duplicate titles; I could already have a note in the JSON file with the title of
secret. I can come along and try to add a new note with the title of secret and it
will not throw an error. What I'd like to do is to make the title unique, so that if
there's already a note with that title, it will throw an error, letting you know that
you need to create a note with a different title.
Making the title unique
The first step to make the title unique will be to loop through all of the notes
after we load them in and check whether there are any duplicates. If there are
duplicates, we'll not call the following two lines:
notes.push(note);
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
If there are no duplicates then it's fine, we will call both of the lines shown in the
preceding code block, updating the notes-data file.
Now, we'll be refactoring this function down the line. Things are getting a little
wonky and a little out of control, but for the moment, we can add this
functionality right into the function. Let's go ahead and make a variable called
duplicateNotes.
The duplicateNotes variable will eventually store an array with all of the notes that
already exist inside the notes array that have the title of the note you're trying to
create. Now, this means that if the duplicateNotes array has any items, that's bad.
This means that the note already exists and we should not add the note. The
duplicateNotes variable will get set equal to a call to notes, which is our array of
notes.filter:
var duplicateNotes = notes.filter();
The filter method is an array method that takes a callback. We'll use an arrow
function, and that callback will get called with the argument. In this case, it will
be the singular version; if I have an array of notes, it will be called with an
individual note:
var duplicateNotes = notes.filter((note) => {
});
This function gets called once for every item in the array, and you have the
opportunity to return either true or false. If you return true, it will keep that item
in the array, which will eventually get saved into duplicateNotes. If you return
false, the new array it generates will not have that item inside duplicateNotes
variable. All we want to do is to return true if the titles match, which means that
we can return note.title === title, as shown here:
var duplicateNotes = notes.filter((note) => {
return note.title === title;
});
If the titles are equal, then the preceding return statement will result as true and
the item will be kept in the array, which means that there are duplicate notes. If
the titles are not equal, which is most likely the case, the statement will result as
false, which means that there are no duplicate notes. Now, we can simplify this a
little more using arrow functions.
Arrow functions actually allow you to remove the curly braces if
you only have one statement.
I'll use the arrow function, as shown here:
var duplicateNotes = notes.filter((note) => note.title === title);
Here, I have deleted everything except note.title === title and added this in front
of the arrow function syntax.
This is perfectly valid using ES6 arrow functions. You have your arguments on
the left, the arrow, and on the right, you have one expression. The expression
doesn't take a semicolon and it's automatically returned as the function result.
This means that the code we have here is identical to the code we had earlier,
only it's much simpler and it only takes up one line.
Now that we have this in place, we can go ahead and check the length of the
duplicateNotes variable. If the length of duplicateNotes is greater than 0, this means
that we don't want to save the note because a note already exists with that title. If
it is 0, we'll save the note.
if(duplicateNotes.length === 0) {
}
Here, inside the if condition, we're comparing the notes length with the number
zero. If they are equal, then we do want to push the note onto the notes array and
save the file. I'll cut the following two lines:
notes.push(note);
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
Let's paste them right inside of the if statement, as shown here:
if(duplicateNotes.length === 0) {
notes.push(note);
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
}
If they're not equal, that's okay too; in that case we'll do nothing.
With this in place, we can now save our file and test this functionality out. We
have our notes-data.json file, and this file already has a note with a title of secret2.
Let's rerun the previous command to try to add a new note with that same title:
node app.js add --title=secret2 --body="Some body here"
You're in Terminal, so we'll head back into our JSON file. You can see right here
that we still just have one note:
Now all the titles inside of our application will be unique, so we can use these
titles to fetch and delete notes.
Let's go ahead and test that other notes can still be added. I'll change the title
flag from secret2 to secret, and run that command:
node app.js add --title=secret --body="Some body here"
Inside our notes-data file, you can see both notes show up:
As I mentioned earlier, next we will be doing some refactoring, since the code
that loads the file, and the code that saves the file, will both be used in most of
the functions we have defined and/or will define (that is, the getAll, getNote and
removeNote functions).
Refactoring
In the previous section, you created the addNote function, which works well. It
starts by creating some static variables, then we fetch any existing notes, we
check for duplicates, and if there are none, we push it onto the list, and then we
save the data back into the filesystem.
The only problem is that we'll be doing a lot of these steps over and over again
for every method. For example, with getAll, the idea is to fetch all of the notes,
and send them back to app.js so it can print them to the screen for the user. The
first thing we'll to do inside of the getAll statement is have the same code; we'll
have our try-catch block to fetch the existing notes.
Now, this is a problem because we'll be repeating code throughout the
application. It will be best to break out the fetching of notes and the saving of
notes into separate functions that we can call in multiple locations.
Moving functionality into
individual functions
To resolve the problem, I'd like to get started by creating two new functions:
fetchNotes
saveNotes
The first function, fetchNotes, will be an arrow function, and it will not to take any
arguments since it will be fetching notes from the filesystem, as shown here:
var fetchNotes = () => {
};
The second function, saveNotes, will need to take an argument. It will need to take
the notes array you want to save to the filesystem. We'll set it equal to an arrow
function, and then we'll provide our argument, which I will name notes, as shown
here:
var saveNotes = (notes) => {
};
Now that we have these two functions, we can go ahead and start moving some
of the functionality from addNote up into the individual functions.
Working with fetchNotes
First up, let's do fetchNotes, which will need the following try-catch block.
I'll actually cut it out of addNote and paste it in the fetchNotes function, as shown
here:
var fetchNotes = () => {
try{
var notesString = fs.readFileSync('notes-data.json');
notes = JSON.parse(notesString);
} catch (e) {
}
};
This alone is not enough, because currently we don't return anything from the
function. What we want to do is to return the notes. This means that instead of
saving the result from JSON.parse onto the notes variable, which we haven't
defined, we'll simply return it to the calling function, as shown here:
var fetchNotes = () => {
try{
var notesString = fs.readFileSync('notes-data.json');
return JSON.parse(notesString);
} catch (e) {
}
};
So, if I call fetchNotes in the addNote function, shown as follows, I will get the notes
array because of the return statement in the preceding code.
Now, if there are no notes, maybe there's no file at all; or there is a file, but the
data isn't JSON, we can return an empty array. We'll add a return statement inside
of catch, as shown in the following code block, because remember, catch runs if
anything inside try fails:
var fetchNotes = () => {
try{
var notesString = fs.readFileSync('notes-data.json');
return JSON.parse(notesString);
} catch (e) {
return [];
}
};
Now, this lets us simplify addNote even further. We can remove the empty space
and we can take the array that we set on the notes variable and remove it and
instead call fetchNotes, as shown here:
var addNote = (title, body) => {
var notes = fetchNotes();
var note = {
title,
body
};
With this in place, we now have the exact same functionality we had before, but
we have a reusable function, fetchNotes, which we can use in the addNote function
to handle the other commands that our app will support.
Instead of copying code and having it in multiple places in your file, we've
broken it into one place. If we ever want to change how this functionality works,
whether we want to change the filename or some of the logic such as the try-catch
block, we can change it once instead of having to change it in every function we
have.
Working with saveNotes
Now, the same thing will go for saveNotes just as in the case of the fetchNotes
function. The saveNotes function will take the notes variable and it will say this
using fs.writeFileSync. I will cut out the line in addNote that does this (that is,
fs.writeFileSync('notes-data.json', JSON.stringfy(notes));) and paste it in the saveNotes
function, as shown here:
var saveNotes = (notes) => {
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
};
Now, saveNotes doesn't need to return anything. In this case, we'll copy the line in
saveNotes and then call saveNotes in the if statement of the addNote function, as
shown in the following code:
if (duplicateNotes.length === 0) {
notes.push(note);
saveNotes();
}
This might seem like overkill, we've essentially taken one line and replaced it
with a different line, but it is a good idea to start getting in the habit of creating
reusable functions.
Now, calling saveNotes with no data is not going to work, we want to pass in the
notes variable, which is our notes array defined earlier in the saveNotes function:
if (duplicateNotes.length === 0) {
notes.push(note);
saveNotes(notes);
}
With this in place, the addNote function should now work as it did before we did
any of our refactoring.
Testing the functionality
The next step in the process will be to test this out by creating a new note. We
already have two notes, with a title of secret and a title of secret2 in notes-data.json,
let's make a third one using the node app.js command in Terminal. We'll use the
add command and pass in a title of to buy and a body of food, as shown here:
node app.js add --title="to buy" --body="food"
This should create a new note, and if I run the command, you can see we don't
have any obvious errors:
Inside of our notes-data.json file, if I scroll to the right, we have our brand new
note as a title of to buy and a body of food:
So, everything is working as expected even though we've refactored the code.
Now, the next thing I want to do inside addNote is take a moment to return the note
that's being added, and that will happen right after saveNotes comes back. So we'll
return note:
if (duplicateNotes.length === 0) {
notes.push(note);
saveNotes(notes);
return note;
}
This note object will get returned to whoever called the function, and in this case,
it will get returned to app.js, where we called it in the if else block of the add
command in the app.js file. We can make a variable to store this result and we
can call it note:
if (command === 'add')
var note = notes.addNote(argv.title, argv.body);
If note exists, then we know that the note was created. This means that we can go
ahead and print a message, like Note created, and we can print the note title and the
note body. Now, if note does not exist, if it's undefined, this means that there was
a duplicate and that title already exists. If that's the case, I want you to print an
error message such as Note title already in use.
There's a ton of different ways you could do this. The goal, though,
is to print two different messages depending on whether or not a
note was returned.
Now, inside addNote, if the duplicateNotes if statement never runs, we don't have an
explicit call to return. But as you know, in JavaScript, if you don't call return,
then undefined automatically is returned. This means that if duplicateNotes.length is
not equal to zero, undefined will be returned and we can use that as the condition
for our statement.
The first thing I'll do here is to create an if statement, right next to the note
variable we defined in app.js:
if (command === 'add') {
var note = notes.addNote(argv.title, argv.body);
if (note) {
}
This will be an object if things went well, and it will be undefined if things went
poorly. This code in here is only ever going to run if it's an object. The Undefined
result will fail the condition inside of JavaScript.
Now, if the note was created successfully, what we'll do is to print a little message
to the screen, using the following console.log statement:
if (note) {
console.log('Note created');
}
If things went poorly, inside the else clause, we can call console.log, and we can
print something like Note title taken, as shown here:
if (note) {
console.log('Note created');
} else {
console.log('Note title taken');
}
Now, the other thing that we want to do if things went well is print the notes
content. I'll do this by first using console.log to print a couple of hyphens. This
will create a little space above my note. Then I can use console.log twice: the first
time we'll print the title, I'll add Title: as a string to show you what exactly you're
seeing, then I can concatenate the title, which we have access to in note.title, as
shown in this code:
if (note) {
console.log('Note created');
console.log('--');
console.log('Title: ' + note.title);
Now, the preceding syntax uses an ES5 syntax; we can swap this out with an
ES6 syntax using what we've already talked about: template strings. We'll add
Title, a colon, and then we can use our dollar sign with our curly braces to inject
the note.title variable, as shown here:
console.log(`Title: ${note.title}`);
Similarly, I'll add note.body after this to print out the body of the note. With this in
place, the code should look like:
if (command === 'add') {
var note = note.addNote(argv.title, argv.body);
if (note) {
console.log('Note created');
console.log('--');
console.log(`Title: ${note.title}`);
console.log(`Body: ${note.body}`);
} else {
console.log('Note title taken');
}
Now, we should be able to run our app and see both of the title and body notes
printed. In Terminal, I'll rerun the previous command. This will try to create a
note with to buy, which already exists, so we should get an error message, and
right here you can see Note title taken:
Now, we can rerun the command, changing the title to something else, such as to
buy from store. This is a unique note title so the note should get created without any
problems:
node app.js add --title="to buy from store" --body="food"
As shown in the preceding output, you can see that we get just that: we have our
Note created message, our little spacer, and our title along with the body.
The addNote command is now complete. We have an output when the command
actually finishes, and we have all the code that runs behind the scenes to add the
note to the data that gets stored in our file.
Summary
In this chapter, you learned that parsing in process.argv can be a real pain. We
would have to write a lot of manual code to parse out those hyphens, the equal
signs, and the optional quotes. However, yargs can do all of that for us and it
puts it on a really simple object we can access. You also learned how to work
with JSON inside Node.js.
Next, we filled out the addNote function. We're able to add notes using the
command line, and we're able to save those notes into a JSON file. Finally, we
pulled out a lot of the code from addNote into separate functions, fetchNotes and
saveNotes, which are now separate, and they're able to be reused throughout the
code. When we start filling out the other methods, we can simply call fetchNotes
and saveNotes instead of having to copy the contents over and over again to every
new method.
In the next chapter, we'll continue our journey on node fundamentals. We'll
explore some more concepts related to node, such as debugging; we'll work on
the read and remove notes commands. Apart from this, we'll also learn about the
advanced features of yargs and the arrow function.
Node Fundamentals – Part 3
We start adding support for all the other commands inside of the notes
application. We'll take a look at how we can create our read command. The read
command will be responsible for fetching the body of an individual note. It will
fetch all the notes and print them to the screen. Now, aside from all of that, we'll
be looking at debugging broken apps, and we'll look at some new ES6 features.
You'll learn how to use the built-in Node debugger.
Then, you will learn a little bit more about how we can configure yargs for the
command-line interface applications. We'll learn how to set up the commands,
their descriptions, and the arguments. We'll be able to set various properties on
the arguments, for example, whether or not they're required, and others.
Removing a note
In this section, you will write the code for removing a note when someone uses
that remove command, and they pass in the title of the note they want to remove.
In the previous chapter, we already created some utility functions that help us
with fetching and saving notes, so the code should actually be pretty simple.
Using the removeNote function
The first step in the process is to fill out the removeNote function, which we defined
in the previous chapters, and this will be your challenge. Let's remove console.log
from the removeNote function in the notes.js file. You only need to write three lines
of code to get this done.
Now, the first line will fetch the notes, then the job will be to filter out the notes,
removing the one with title of argument. That means we want to go through all
of the notes in the notes array, and if any of them have a title that matches the
title we want to remove, we want to get rid of them. And this can be done using
the notes.filter function we used earlier. All we have to do is switch the equality
statement in the duplicateNotes function from equals to not equals, and this code
will do just that.
It will go through the notes array. Every time it finds a note that doesn't match
the title it will keep it, which is what we want, and if it does find the title it will
return false and remove it from the array. And then we will add the third line,
which is to save the new notes array:
var removeNote = (title) => {
// fetch notes
// filter notes, removing the one with title of argument
// save new notes array
};
The preceding code lines are the only three lines you need to fill out. Don't
worry about returning anything from removeNote or filling out anything inside of
app.js.
The first thing we will do for the fetchNotes line is to create a variable called notes,
just like we did in addNote in the previous chapter, and we'll set it equal to the
return result from fetchNotes:
var removeNote = (title) => {
var notes = fetchNotes();
// filter notes, removing the one with title of argument
// save new notes array
};
At this point our notes variable stores an array of all of the notes. The next thing
we need to do is filter our notes.
If there is a note that has this title, we want to remove it. This will be done by
creating a new variable, and I'll call this one filteredNotes. Here we'll set
filteredNotes equal to the result that will come back from notes.filter, which we
already used up previously:
var removeNote = (title) => {
var notes = fetchNotes();
// filter notes, removing the one with title of argument
var filteredNotes = notes.filter();
// save new notes array
};
We know that notes.filter takes a function as its one and only argument, and that
function gets called with the individual item in the array. In this case it would be
a note. And we can do this all on one line using the ES6 arrow syntax.
If we have only one statement, we don't need to open and close
curly braces.
That means right here we can return true if note.title does not equal the title that's
passed into the function:
var removeNote = (title) => {
var notes = fetchNotes();
var filteredNotes = notes.filter((note) => note.title !== title);
// save new notes array
};
This will populate filteredNotes with all of the notes whose titles do not match the
one passed in. If the title does match the title passed in, it will not be added to
filteredNotes because of our filter function.
The last thing to do is to call saveNotes. Right here, we'll call saveNotes passing in
the new notes array which we have under the filteredNotes variable:
var removeNote = (title) => {
var notes = fetchNotes();
var filteredNotes = notes.filter((note) => note.title !== title);
saveNotes(filteredNotes);
// save new notes array
};
If we were to pass in notes, it wouldn't work as expected; we're filtering the
notes out but we're not actually saving those notes, so it will not get removed
from the JSON. We need to pass filteredNotes as shown in the preceding code.
And we can test these by saving the file and trying to remove one of our notes.
I'll try to remove secret2 from the notes-data.json file. That means all we need to
do is run the command, which we called remove, that is specified over in app.js,
(refer to the following code image, and then it will call our function).
I'll run Node with app.js, and we'll pass in the remove command. The only
argument we need to provide for remove is the title; there's no need to provide
the body. I'll set this equal to secret2:
node app.js remove --title=secret2
As shown in the screenshot, if I hit enter you can see we don't get any output.
Although we do have the command remove printing, there is no message saying
whether or not a note was removed, but we'll add that later in the section.
For now, we can check the data. And right here you can see secret2 is nowhere in
sight:
This means our remove method is indeed working as expected. It removed the
note whose title matched and it kept all the notes whose title was not equal to
secret2, exactly what we wanted.
Printing a message of removing
notes
Now, the next thing we'll do is print a message depending on whether or not a
note was actually removed. That means app.js, which calls the removeNote function,
will need to know whether or not a note was removed. And how do we figure
that out? How can we possibly return that given the information we have in
notes.js removeNotes function?
Well, we can, because we have two really important pieces of information. We
have the length of the original notes array and we have the length of the new
notes array. If they're equal then we can assume that no note was removed. If
they are not equal, we'll assume that a note was removed. And that is exactly
what we'll do.
If the removeNote function returns true, that means a note was removed; if it returns
false, that means a note was not removed. In the removeNotes function we can add
return, as shown in the following code. We'll check if notes.length does not equal
filteredNotes.length:
var removeNote = (title) => {
var notes = fetchNotes();
var filteredNotes = notes.filter((note) => note.title !== title);
saveNotes(filteredNotes);
return notes.length !== filteredNotes.length;
};
If they're not equal it will return true, which is what we want because a note was
removed. If they're equal it will return false, which is great.
Now, inside of app.js we can add a few lines in the removeNote, else if block to
make the output for this command a little nicer. The first thing to do is to store
that Boolean. I'll make a variable called noteRemoved and we'll set that equal to the
return, result as shown in the following code, which will either be true or false:
} else if (command == 'remove') {
var noteRemoved = notes.removeNote(argv.title);
}
On the next line, we can create our message, and I'll do this all on one line using
the ternary operator. Now, the ternary operator lets you specify a condition. In
our case, we'll use a var message and it will be set equal to the condition
noteRemoved, which will be true if a note was removed and false if it wasn't.
Now, the ternary operator can be a little confusing, but it's really
useful inside JavaScript and Node.js. The format for the ternary
operator is first we add the condition, question mark, the truthy
expression to run, colon, and then the falsy expression to run.
After the condition, we'll put a space with a question mark and a space; this is
the statement that will run if it's true. If the noteRemoved condition passes, what we
want to do is set message equal to Note was removed:
var message = noteRemoved ? 'Note was removed' :
Now, if noteRemoved is false, we can specify that condition right after the colon in
the previous statement. Here, if there is no note removed we'll use the text Note
not found:
var message = noteRemoved ? 'Note was removed' : 'Note not found';
Now with this in place, we can test out our message. The last thing to do is print
the message to the screen using console.log passing in message:
var noteRemoved = notes.removeNote(argv.title);
var message = noteRemoved ? 'Note was removed' : 'Note not found';
console.log(message);
This lets us avoid if statements that make our else-if clause to remove
unnecessarily complex.
Back inside of Atom we can rerun the last command, and in this case no note
will get removed because we already deleted it. And when I run it, you can see
that Note not found prints to the screen:
Now I'll remove a note that does exist; in notes-data.json I have a note with a title
of secret as shown here:
Let's rerun the command removing the 2 from the title in Terminal. When I run
this command, you can see Note was removed prints to the screen:
That is it for this section; we now have our remove command in place.
Reading note
In this section, you will be responsible for filling out the rest of the read
command. Now, the read command does have an else-if block to find in app.js
where we call getNote:
} else if (command === 'read') {
notes.getNote(argv.title);
getNote is defined over inside notes.js, even though currently it just prints out
some dummy text:
var getNote = (title) => {
console.log('Getting note', title);
};
What you'll need to do in this section is wire up both of these functions.
First up, you will need to do something with the return value from getNote. Our
getNote function will return the note object if it finds it. If it doesn't, it will return
undefined just like we do for addNote discussed in the section Adding and saving
note, in the previous chapter.
After you store that value, you'll do some printing using console.log, similar to
what we have here:
if (command === 'add') {
var note = notes.addNote(argv.title, argv.body);
if (note) {
console.log('Note created');
console.log('--');
console.log(`Title: ${note.title}`);
console.log(`Body: ${note.body}`);
} else {
console.log('Note title taken');
}
Obviously, Note created will be something like Note read and Note title taken will be
something like Note not found, but the general flow is going to be exactly the
same. Now, once you have that wired up inside of app.js, you can move on to
notes.js, filling out the function.
Now, the function inside of notes.js isn't going to be that complex. All you need
to do is fetch the notes, like we've done in previous methods, then you're going
to use notes.filter, which we explored to only return notes whose title matches
the title passed in as the argument. Now, in our case this is either going to be
zero notes, which means the note is not found, or it's going to be one note, which
means we've found the note that the person wants to return.
Next, we do need to return that note. It's important to remember the return value
from notes.filter is always going to be an array, even if that array only has one
item. What you're going to need to do is return the first item in the array. If that
item doesn't exist that's fine, it'll return undefined, as we want. If it does exist,
great, that means we found the note. This method only requires three lines of
code, one for fetching, one for filtering, and the return statement. Now, once you
have all that done we'll test it out.
Using the getNote function
Let's work on this method. Now, the first thing I'll do is fill out, inside of app.js, a
variable called note which is going to store the return value from getNote:
} else if (command === 'read') {
var note = notes.getNote(argv.title);
Now, this could be an individual note object or it could be undefined. In the next
line, I can use an if statement to print the message if it exists, or if it does not
exist. I'll use if note, and I am going to attach an else clause:
} else if (command === 'read') {
var note = notes.getNote(argv.title);
if (note) {
} else {
}
This else clause will be responsible for printing an error if the note is not found.
Let's get started with that first since it's pretty simple, console.log, Note not found, as
shown here:
if (note) {
} else {
console.log('Note not found');
}
Now that we have our else clause filled out we can fill out the if statement. For
this, I'll print a little message, console.log ('Note found') will get the job done. Then
we can move on to printing the actual note details, and we already have that code
in place. We are going to add the hyphenated spacer, then we have our note title
and our note body as shown here:
if (note) {
console.log('Note found');
console.log('--');
console.log(`Title: ${note.title}`);
console.log(`Body: ${note.body}`);
} else {
console.log('Note not found');
}
Now that we're done with the inside of app.js, we can move into the notes.js file
and fill out the getNote method because currently it doesn't do anything with the
title that gets passed in.
Inside notes, what you needed to do was fill out those three lines. The first one is
going to be responsible for fetching the notes. We already have did that before
with the fetchNotes function in the previous section:
var getNote = (title) => {
var notes = fetchNotes();
};
Now that we have our notes in place we, can call notes.filter, returning all of the
notes. I'll make a variable called filteredNotes, setting it equal to notes.filter. Now,
we know that the filter method takes a function, I'll define an arrow function (=>)
just like this:
var filteredNotes = notes.filter(() => {
});
Inside the arrow function (=>), we'll get the individual note passed in, and we'll
return true when the note title, the title of the note we found in our JSON file,
equals, using triple equals, title:
var filteredNotes = notes.filter(() => {
return note.title === title;
});
};
This will return true when the note title matches and false if it doesn't.
Alternatively, we can use arrow functions, and we only have one line, as shown
following, where we return something; we can cut out our condition, remove the
curly braces, and simply paste that condition right here:
var filteredNotes = notes.filter((note) => note.title === title);
This has the exact same functionality, only it's a lot shorter and easier to look at.
Now that we have all of the data, all we need to do is return something, and we'll
return the first item in the filteredNotes array. Next, we'll grab the first item,
which is the index of zero, and then we just need to return it using the return
keyword:
var getNote = (title) => {
var notes = fetchNotes();
var filteredNotes = notes.filter((note) => note.title === title);
return filteredNotes[0];
};
Now, there is a chance that filteredNotes, the first item, doesn't exist, and that's
fine, it's going to return undefined, in which case our else clause will run,
printing Note not found. If there is a note, great, that's the note we want to print,
and over in app.js we do just that.
Running the getNote function
Now that we have this in place we can test out this brand new functionality
inside of Terminal by running our app using node app.js. I'll use the read
command, and I'll pass in a title equal to some string that I know does not exist
inside of a title in the notes-data.json file:
node app.js read --title="something here"
When I run the command, we get Note not found, as shown here, and this is exactly
what we want:
Now, if I do try to fetch a note where the title does exist, I would expect that note
to come back.
In the data file I have a note with a title of to buy; let's try to fetch that one. I'll
use the up arrow key to populate the previous command and replace the title
with to space, buy, and hit enter:
As shown in the previous code, you can see Note found prints to the screen, which
is fantastic. Following Note found we have our spacers and following that we have
the title, which is to buy, and the body, which is food, exactly as it appears inside
of the data file. With this in place, we are done with the read command.
The DRY principle
Now, there is one more thing I want to tackle before we wrap up this section.
Inside app.js we now have the same code in two places. We have the space or
title body in the add command as well as in the read command:
if (command === 'add') {
var note = notes.addNote(argv.title, argv.body);
if (note) {
console.log('Note created');
console.log('--');
console.log(`Title: ${note.title}`);
console.log(`Body: ${note.body}`);
} else {
console.log('Note title taken');
}
} else if (command === 'list') {
notes.getAll();
} else if (command === 'read') {
var note = notes.getNote(argv.title);
if (note) {
console.log('Note found');
console.log('--');
console.log(`Title: ${note.title}`);
console.log(`Body: ${note.body}`);
} else {
console.log('Note not found');
}
When you find yourself copying and pasting code, it's probably best to break that
out into a function that both locations call. This is the DRY principle, which
stands for Don't Repeat Yourself.
Using the logNote function
In our case, we are repeating ourselves. It would be best to break this out into a
function that we can call from both places. In order to do this, all we're going to
do is make a function in notes.js called logNote.
Now, in notes.js, down following the removeNote function, we can make that brand
new function a variable called logNote. This is going to be a function that takes
one argument. This argument will be the note object because we want to print
both the title and the body. As shown here, we'll expect the note to get passed in:
var logNote = (note) => {
};
Now, filling out the logNote function is going to be really simple, especially when
you're solving a DRY issue, because you can simply take the code that's
repeated, cut it out, and paste it right inside the logNote function. In this case the
variable names line up already, so there is no need to change anything:
var logNote = (note) => {
console.log('--');
console.log(`Title: ${note.title}`);
console.log(`Body: ${note.body}`);
};
Now that we have the logNote function in place, we can change things over in
app.js. In app.js, where we have removed the console.log statements we can call
notes.logNote, passing in the note object just like this:
else if (command === 'read') {
var note = notes.getNote(argv.title);
if (note) {
console.log('Note found');
notes.logNote(note);
} else {
console.log('Note not found');
}
And we can do the same thing in case of the add command if block. I can remove
these three console.log statements and call notes.logNote, passing in note:
if (command === 'add') {
var note = notes.addNote(argv.title, argv.body);
if (note) {
console.log('Note created');
notes.logNote(note);
} else {
console.log('Note title taken');
}
And now that we have this in place, we can rerun our program and hopefully
what we see is the exact same functionality.
The last thing to do before we rerun the program is export the logNote function in
exports module in notes.js file. LogNote is going to get exported and we're using the
ES6 syntax to do that:
module.exports = {
addNote,
getAll,
getNote,
removeNote,
logNote
};
With this in place, I can now rerun the previous command from Terminal using
up and hit enter:
node app.js read --title="to buy"
As shown, we get Note found printing to the screen, with the title and the body just
like we had before. I'm also going to test out the add command to make sure that
one's working, node app.js add; we will use a title of things to do and a body of go
to post office:
node app.js add --title="things to do" --body="go to post office"
Now, when I hit enter, we would expect the same log to print as it did before for
the add command, and that's exactly what we get:
Note created prints, we get our spacer, and then we get our title and our body.
In the next section, we're going to cover one of the most important topics in the
book; which is debugging. Knowing how to properly debug programs is going to
save you literally hundreds of hours over your Node.js career. Debugging can be
really painful if you don't have the right tools, but once you know how it's done,
it really isn't that bad and it can save you a ton of time.
Debugging
In this section, we're going to use the built-in debugger, which can look a little
complex because it's run inside of the command line. That means that you have
to use the command-line interface, which is not always the most pleasant thing
to look at. In the next section, though, we are going to be installing a third-party
tool that uses Chrome DevTools in order to debug your Node app. That one
looks great because the Chrome DevTools are fantastic.
Executing a program in debug
mode
Before going ahead, we will learn that we do need to create a place to play
around with debugging and that's going to happen in a playground file, since the
code we're going to write is not going to be important to the notes app itself.
Inside the notes app I'll make a new file called debugging.js:
In debugging.js we're going to start off with a basic example. We're going to make
an object called person, and on that object for the moment, we're going to set one
property name. Set it equal to your name, I'll set mine equal to the string Andrew as
shown:
var person = {
name: 'Andrew'
};
Next up we're going to set another property, but in the next line, person.age. I'll set
mine equal to my age, 25:
var person = {
name: 'Andrew'
};
person.age = 25;
Then we're going to add another statement that changes the name, person.name
equals something like Mike:
var person = {
name: 'Andrew'
};
person.age = 25;
person.name = 'Mike';
Finally, we're going to console.log the person object, the code is going to look like
this:
var person = {
name: 'Andrew'
};
person.age = 25;
person.name = 'Mike';
console.log(person);
Now, we actually already have a form of debugging in this example, we have a
console.log statement.
As you're going through the Node application development process, you may or
may not have used console.log to debug your app. Maybe something's not
working as expected and you want to figure out exactly what that variable has
stored inside of it. For example, if you have a function that solves a math
problem, maybe at one part in the function the equation is wrong and you're
getting a different result.
Using console.log can be a pretty great way to do that, but it's super limited. We
can view that by running it from Terminal, I'll run the following command for
this:
node playground/debugging.js
When I run the file, I do get my object printed out to the screen, which is great,
but, as you know, if you want to debug something besides the person object you
have to add another console.log statement in order to do that.
Imagine you have something like our app.js file, you want to see what command
equals, then you want to see what argv equals, it could take a lot of time to add
and remove those console.log statements. There is a better way to debug. This is
using the Node debugger. Now, before we make any changes to the project, we'll
take a look at how the debugger works inside of Terminal, and as I warned you in
the beginning of the section, the built-in Node debugger, while it is effective, is a
little ugly and hard to use.
For now, though, we are going to run the app much the same way, only this time
we're going to type node inspect. Node debug is going to run our app completely
differently from the regular Node command. We're running the same file in the
playground folder, it's called debugging.js:
node inspect playground/debugging.js
When you hit enter, you should see something like this:
In the output, we can ignore the first two lines. This essentially means that the
debugger was set up correctly and it's able to listen to the app running in the
background.
Next, we have our very first line break in playground debugging on line one, and
right following to it you can see line one with a little caret (>) next to it. When
you first run your app in debug mode, it pauses before it executes the first
statement. When we're paused on a line like line one, that means the line has not
executed, so at this point in time we don't even have the person variable in place.
Now, as you can see in the preceding code, we haven't returned to the command
line, Node is still waiting for input, and there are a few different commands we
can run. For example, we can run n, which is short for next. You can type n, hit
enter, and this moves on to the next statement.
The next statement we have, the statement on line one, was executed, so the
person variable does exist. Then I can use n again to go to the next statement
where we declare the person.name property, updating it from Andrew to Mike:
Notice, at this point, age does exist because that line has already been executed.
Now, the n command goes statement by statement through your entire program.
If you realize that you don't want to do that through the whole program, which
could take a lot of time, you can use c. The c command is short for Continue,
and that continues to the very end of the program. In the following code, you can
see our console.log statement runs the name Mike and the age 25:
This is that's a quick example of how to use the debug keyword.
Now, we actually didn't do any debugging, we just ran through the program
since it is a little foreign in terms of writing these commands, such as next and
continue, I decided to do a dry run once with no debugging. You can use control
+ C to quit the debugger and get returned back to Terminal.
I'll use clear to clear all the output. Now that we have a basic idea about how we
can execute the program in debug mode, let's take a look at how we can actually
do some debugging.
Working with debugging
I'll rerun the program using the up arrow key twice to return to the Node debug
command. Then, I'll run the program, and I'll hit next twice, n and n:
At this point in time, we are on line seven, that is where the line break currently
is. From here we can do some debugging using a command called repl, which
stands for Read Evaluate Print Loop. The repl command, in our case, brings
you to an entirely separate area of the debugger. When you hit it you're essentially
in a Node console:
You can run any Node commands, for example, I can use console.log to print
something like test, and test prints up right there.
I can make a variable a that is equal to 1 plus 3, then I can reference a and I can
see it's equal to 4 as shown:
More importantly, we have access to the current program as it sits, meaning as it
was before line seven was executed. We can use this to print out person, and as
shown in the following code, you can see the person's name is Andrew because line
seven hasn't executed and the age is 25, exactly as it appears in the program:
This is where debugging gets really useful. Being able to look at the program
paused at a certain point in time is going to make it really easy to spot errors. I
could do anything I want, I could print out the person name property, and that
prints Andrew to the screen, as shown here:
Now, once again, we still have this problem. I have to hit next through the
program. When you have a really long program, there could literally be hundreds
or thousands of statements that need to run before you get to the point you care
about. Obviously that is not ideal, so we're going to look at a better way.
Let's quit repl using control + C; now we're back at the debugger.
From here we are going to make a quick change to our application in debugging.js.
Let's say we want to pause line seven between the person age property update
and the person name property update. In order to pause, what we're going to do
is run the statement debugger:
var person = {
name: 'Andrew'
};
person.age = 25;
debugger;
person.name = 'Mike';
console.log(person);
When you have a debugger statement exactly like previous, it tells the Node
debugger to stop here, which means instead of using n (next) to go statement by
statement, you can use c (continue), which is going to continue until either the
program exits or it sees one of the debugger keywords.
Now, over in Terminal, we're going to rerun the program exactly like we did
before. This time around, instead of hitting n twice, we're going to use c to
continue:
Now, when we first used c, it went to the end of the program, printing out our
object. This time around it's going to continue until it finds that debugger keyword.
Now, we can use repl, access anything we like, for example, person.age, shown in
this code:
Once we're done debugging, we can quit and continue through the program.
Again, we can use control + C to quit repl and the debugger.
All real debugging pretty much happens with the debugger keyword. You put it
wherever you want on your program, you run the program in debug mode,
eventually it gets to the debugger keyword and you do something. For example
you explore some variable values, you run some functions, or you play around
with a code to find the error. No one really uses n to print through the program,
finding the line that causes the problem. That takes way too much time and it's
just not realistic.
Using debugger inside the notes
application
Now that you know a little bit about the debugger, I want you to use it inside our
notes application. What we will do inside notes.js is add the debugger statement in
logNote function as the first line of the function. Then I will run the program in
debug mode, passing in some arguments that will cause logNote to run; for
example, reading a note, after the note gets fetched, it's going to call logNote.
Now, once we have the debugger keyword in the logNote function and run it in
debug mode with those arguments, the program should stop at this point. Once
the program starts in debug mode, we'll use c to continue, and it'll pause. Next,
we'll print out the note object and make sure it looks okay. Then, we can quit repl
and quit the debugger.
Now, first we are adding the debugger statement right here:
var logNote = (note) => {
debugger;
console.log('--');
console.log(`Title: ${note.title}`);
console.log(`Body: ${note.body}`);
};
We can save the file, and now we can move into Terminal; there's no need to do
anything else inside our app.
Inside Terminal we're going to run our app.js file, node debug app.js, because we
want to run the program in debug mode. Then we can pass in our arguments,
let's say the read command, and I'll pass in a title, "to buy" as shown here:
node debug app.js read --title="to buy"
In this case I have a note with the title "to buy", as shown here:
Now, when I run the preceding command, it's going to pause before that first
statement runs, this is expected:
I can now use c to continue through the program. It's going to run as many
statements as it takes for either the program to end or for the debugger keyword to
be found, and as shown in the following code, you can see the debugger was found
and our program has stopped on line 49 of notes.js:
This is exactly what we wanted to do. Now, from here, I'll go into repl and print
out note argument, and as shown in the following code, you can see we have the
note with the title of to buy and the body food:
Now, if there was an error in this statement, maybe the wrong thing was printing
to the screen, this would give us a pretty good idea as to why. Whatever gets
passed into the note is clearly being used inside of the console.log statements, so if
there was an issue with what's printing, it's most likely an issue with what gets
passed into the logNote function.
Now that we've printed the note variable, we can shut down repl, and we can use
control + C or quit to quit the debugger.
Now we're back at the regular Terminal and we have successfully completed the
debugging inside the Node application. In the next section, we're going to look at
a different way to do the same thing, a way with a much nicer graphic user
interface that I find a lot easier to navigate and use.
Listing notes
Now that we've made some awesome progress on debugging, let's go back to the
commands for our app, because there is only one more to fill out (we have
covered the add, read, and remove commands in the Chapter 3, Node Fundamentals -
Part 2, and this chapter, respectively). It's the list command, and it's going to be
really easy, there is nothing complex going on in the case of the list command.
Using the getAll function
In order to get started, all we need to do is fill out the list notes function, which
in this case we called getAll. The getAll function is responsible for returning every
single note. That means it's going to return an array of objects, an array of all of
our notes.
All we have to do that is to return fetchNotes, as shown here:
var getAll = () => {
return fetchNotes();
}
There's no need to filter, there's no need to manipulate the data, we just need to
pass the data from fetchNotes back through getAll. Now that we have this in place,
we can fill out the functionality over inside of app.js.
We have to create a variable where we can store the notes, I was going to call it
notes, but I probably shouldn't because we already have a notes variable
declared. I'll create another variable, called allNotes, setting it equal to the return
value from getAll, which we know because we just filled out returns all the notes:
else if (command === 'list') {
var allNotes = notes.getAll();
}
Now I can use console.log to print a little message and I'll use template strings so I
can inject the actual number of notes that are going to be printed.
Inside the template strings, I'll add Printing, then the number of notes using the $
(dollar) sign and the curly braces, allNotes.length: that's the length of the array
followed by notes with the s in parenthesis to handle both singular and plural
cases, as shown in the following code block:
else if (command === 'list') {
var allNotes = notes.getAll();
console.log(`Printing ${allNotes.length} note(s).`);
}
So, if there were six notes, it would say printing six notes.
Now that we have this in place, we have to go about the process of actually
printing each note, which means we need to call logNote once for every item in
the allNotes array. To do, this we'll use forEach, which is an array method similar to
filter.
Filter lets you manipulate the array by returning true or false to keep items or
remove items; forEach simply calls a callback function once for each item in the
array. In this case we can use it using allNotes.forEach, passing in a callback
function. Now, that callback function will be an arrow function (=>) in our case,
and it will get called with the note variable just like filter would have. And all
we'll call is notes.logNote, passing in the note argument, which is right here:
else if (command === 'list') {
var allNotes = notes.getAll();
console.log(`Printing ${allNotes.length} note(s).`);
allNotes.forEach((note) => {
notes.logNote(note);
});
}
And now that we have this in place, we can actually simplify it by adding the
logNote call, as shown in here:
else if (command === 'list') {
var allNotes = notes.getAll();
console.log(`Printing ${allNotes.length} note(s).`);
allNotes.forEach((note) => notes.logNote(note));
}
This is the exact same functionality, only using the expression syntax. Now that
we have our arrow function (=>) in place, we are calling notes.logNote once for
each item in the all notes array. Let's save the app.js file and test this out over in
Terminal.
In order to test out the list command, all I'll use is node app.js with the command
list. There is no need to pass in any arguments:
node app.js list
When I run this, I do get Printing 3 note(s) and then I get my 3 notes to buy, to buy
from store, and things to do, as shown in the following code output, which is
fantastic:
With this in place, all of our commands are now working. We can add notes,
remove notes, read an individual note, and list all of the notes stored in our
JSON file.
Moving on to the next section, I want to clean up some of the commands. Inside
app.js and notes.js, we have some console.log statements that are printing out a few
things we no longer need.
At the very top of app.js, I am going to remove the console.log('Starting app.js')
statement, making the constant fs the first line.
I'll also remove the two statements: console.log('Command: ', command) and
console.log('Yargs', argv) that print the command and the yargs variable value.
Inside notes.js, I will also remove the console.log('Stating notes.js') statement at
the very top of that file, since it is no longer necessary, putting constant fs at the
top.
It was definitely useful when we first started exploring different files, but now
we have everything in place, there's no need. If I rerun the list command, this
time you can see it looks a lot cleaner:
Printing three notes is the very first line showing up. With this in place, we have
done our commands.
In the next section, we're going to take a slightly more in-depth look at how we
can configure yargs. This is going to let us require certain arguments for our
commands. So if someone tries to add a note without a title, we can warn the
user and prevent the program from executing.
Advanced yargs
Before we get into the advanced discussion of yargs, first, I want to pull up the
yargs docs so that you at least know where the information about yargs is
coming from. You can get it by Googling npm yargs. We're going to go to the yargs
package page on npm. This has the documentation for yargs, as shown here:
Now there is no table of contents for the yargs docs, which makes it kind of
difficult to navigate. It starts off with some examples that don't go in any
particular order, and then eventually it gets into a list of all the methods you have
available, and that's what we're looking for.
So I'll use command + F (Ctrl + F) to search the page for methods, and as shown
in the following screenshot, we get the methods header, which is the one we're
looking for:
If you scroll down on the page, we start to see an alphabetical list of all the
methods you have access to inside of yargs. We're specifically looking for
.command; this is the method we can use to configure all four of our commands: the
add, read, remove and list notes:
We're going to specify which options they require, if any, and we can also set up
things like descriptions and help functionality.
Using chaining syntax on yargs
Now in order to get started, we need to make some changes inside of app.js.
We're going to start with the add command (for more information, please refer to
the Adding and saving notes section in the previous chapter).
We want to add a few helpful pieces of information in argv function inside app.js,
that will:
Let yargs verify the add command is ran appropriately, and
Let the user know how the add command is meant to be executed
Now we are going to be chaining property calls, which means right before I
access .argv I want to call .command, and then I'll call .argv on the return value from
command as shown here:
const argv = yargs
.command()
.argv;
Now this chaining syntax probably looks familiar if you've used jQuery, a lot of
different libraries are supported. Once we call .command on yargs, we're going to
pass in three arguments.
The first one is the command name, exactly how the user is going to type it in
Terminal, in our case it's going to be add:
const argv = yargs
.command('add')
.argv;
Then we're going to pass another string in, and this is going to be a description
of what the command does. It is going to be some sort of English readable
description that a user can read to figure out weather that's the command that
they want to run:
const argv = yargs
.command('add', 'Add a new note')
.argv;
The next one is going to be an object. This is going to be the options object that
lets us specify what arguments this command requires.
Calling the .help command
Now before we get into the options object, let's add one more call right after
command. We're going to call .help, which is a method, so we're going to call it
as a function, and we don't need to pass in any arguments:
const argv = yargs
.command('add', 'Add a new note', {
})
.help()
.argv;
When we add on this help call, it sets up yargs to return some really useful
information when someone runs the program. For example, I can run the node
app.js command with the help flag. The help flag is added because we called that
help method, and when I run the program, you can see all of the options we have
available:
node app.js --help
As shown in the preceding output, we have one command, add Add a new note, and
a help option for the current command, help. And the same thing holds true if we
run the node app.js add command with help as shown here:
node app.js add --help
In this output, we can view all of the options and arguments for add command,
which in this case happens to be none because we haven't set those up:
Adding the options object
Let's add options and arguments back inside Atom. In order to add properties,
we're going to update the options object, where the key is the property name,
whether it's title or body, and the value is another object that lets us specify how
that property should work, as shown here:
const argv = yargs
.command('add', 'Add a new note', {
title: {
}
})
.help()
.argv;
Adding the title
In the case of title, we would add the title on the left-hand side, and we would
put our options object on the right-hand side. Inside the title, we're going to
configure three properties describe, demand, and alias:
The describe property will be set equal to a string, and this is going to describe
what is supposed to be passed in for the title. In this case, we can just use Title of
note:
const argv = yargs
.command('add', 'Add a new note', {
title: {
describe: 'Title of note'
}
})
.help()
.argv;
Next we configure demand. It is going to tell yarg whether or not this argument is
required. demand is false by default, we'll set it to true:
const argv = yargs
.command('add', 'Add a new note', {
title: {
describe: 'Title of note',
demand: true
}
})
.help()
.argv;
Now if someone tries to run the add command without the title, it's
going to fail, and we can prove this. We can save app.js, and in
Terminal, we can rerun our previous command removing the help
flag, and when I do that, you see we get a warning, Missing required
argument: title as shown here:
Notice that in the output the title argument, is Title of note, which is
the describe string we used, and it's required on the right side,
letting you know that you have to provide a title when you're
calling that add command.
Along with describe and demand we are going to provide a third option, this is
called alias. The alias lets you provide a shortcut so you don't have to type --
title; you can set the alias equal to a single character like t:
const argv = yargs
.command('add', 'Add a new note', {
title: {
describe: 'Title of note',
demand: true,
alias: 't'
}
})
.help()
.argv;
When you have done that, you can now run the command in Terminal using the
new syntax.
Let's run our add command, node app.js add, instead of --title. We're going to use
-t, which is the flag version, and we can set that equal to whatever we like, for
example, flag title will be the title, and --body will get set equal to body , as shown
in the following code. Note that we haven't set up the body argument yet so there
is no alias:
node app.js add -t="flag title" --body="body"
If I run this command, everything works as expected. The flag title shows up
right where it should, even though we used the alias version which is the letter t,
as shown here:
Adding the body
Now that we have our title configured, we can do the exact same thing for the
body. We'll specify our options object and provide those three arguments:
describe, demand, and alias for body:
const argv = yargs
.command('add', 'Add a new note', {
title: {
describe: 'Title of note',
demand: true,
alias: 't'
},
body: {
}
})
.help()
.argv;
The first one is describe and that one's pretty easy. describe is going to get set equal
to a string, and in this case Body of note will get the job done:
const argv = yargs
.command('add', 'Add a new note', {
title: {
describe: 'Title of note',
demand: true,
alias: 't'
},
body: {
describe: 'Body of note'
}
})
.help()
.argv;
The next one will be demand, and to add a note we are going to need a body. So
we'll set demand equal to true, just like we do up previous for title:
const argv = yargs
.command('add', 'Add a new note', {
title: {
describe: 'Title of note',
demand: true,
alias: 't'
},
body: {
describe: 'Body of note'
demand: true
}
})
.help()
.argv;
And last but not least is the alias. The alias is going to get set equal to a single
letter, I'll use the letter b for body:
const argv = yargs
.command('add', 'Add a new note', {
title: {
describe: 'Title of note',
demand: true,
alias: 't'
},
body: {
describe: 'Body of note'
demand: true,
alias: 'b'
}
})
.help()
.argv;
With this in place, we can now save app.js and inside Terminal, we can take a
moment to rerun node app.js add with the help flag:
node app.js add --help
When we run this command, we should now see the body argument showing up,
and you can even see it shows the flag version, as shown in the following output,
the alias -b (Body of note), and it is required:
Now I'll run node app.js add passing in two arguments t. I'll set that equal to t, and
b setting it equal to b.
When I run the command, everything works as expected:
node app.js add -t=t -b=b
As shown in the preceding output screenshot, a new note was created with a title
of t and a body of b. With this in place, we've now successfully completed the
setup for the add command. We have our add command title, a description, and the
block that specifies the arguments for that command. Now we do have three
more commands to add support for, so let's get started doing that.
Adding support to the read and
remove commands
On the next line, I'll call .command again, passing in the command name. Let's do
the list command first because this one is really easy, no arguments are required.
Then we'll pass in the description for the list command, List all notes, as shown
here:
.command('list', 'List all notes')
.help()
.argv;
Next up, we'll call command again. This time we'll do the command for read. The
read command reads an individual note, so for the description for the read
command, we'll use something like Read a note:
.command('list', 'List all notes')
.command('read', 'Read a note')
.help()
.argv;
Now the read command does require the title argument. That means we are going
to need to provide that options object. I'll take title from add command, copy it,
and paste it in the read command options object:
.command('list', 'List all notes')
.command('read', 'Read a note', {
title: {
describe: 'Title of note',
demand: true,
alias: 't'
}
})
.help()
.argv;
As you probably just noticed, we have repeated code. The title configuration just
got copied and pasted into multiple places. It would be pretty nice if this was
DRY, if it was in one variable we could reference in both locations, in add and
read commands.
Will call command for remove, just following where we called the command for
read. Now, the remove command will have a description. We'll stick with
something simple like Remove a note, and we will be providing an options object:
.command('remove', 'Remove a note', {
})
Now I can add the options object identical to the read command. However, in that
options object, I'll set title equal to titleOptions, as shown here, to avoid the
repetition of code:
.command('remove', 'Remove a note', {
title: titleOptions
})
Adding the titleOption and
bodyOption variables
Now I don't have the titleOptions object created yet so the code would currently
fail, but this is the general idea. We want to create the titleOptions object once
and reference it in all the locations we use it, for add, read and remove command. I
can take titleOptions, and add it for read as well as for add command, as shown
here:
.command('add', 'Add a new note', {
title: titleOptions,
body: {
describe: 'Body of note',
demand: true,
alias: 'b'
}
})
.command('list', 'List all notes')
.command('read', 'Read a note', {
title: titleOptions
})
.command('remove', 'Remove a note', {
title: titleOptions
})
Now, just previous the constant argv, I can create a constant called titleOptions,
and I can set it equal to that object that we defined for add and read command
earlier, which is describe, demand, and alias, as shown here:
const titleOptions = {
describe: 'Title of note',
demand: true,
alias: 't'
};
We now have the titleOptions in place, and this will work as expected. We have
the exact same functionality we did before, but we now have the titleOptions in a
separate object, which follows the DRY principle we discussed in the Reading
note section.
Now, we could also do the same thing for body. It might seem like overkill since
we're only using it in only one location, but if we're sticking to the pattern of
breaking them out into variables, I'll do it in the case of the body as well. Just
following the titleOptions constant, I can create the constant bodyOptions, setting it
equal to the options object we defined in the body, for add command in the
previous subsection:
const bodyOptions = {
describe: 'Body of note',
demand: true,
alias: 'b'
};
With this in place, we are now done. We have add, read, and remove, all with their
arguments set up referencing the titleObject and bodyObject variables defined.
Testing the remove command
Let's test out the remove command in Terminal. I'll list out my notes using node
app.js list, so I can see which notes I have to remove:
node app.js list
I'll remove the note with the title t, using the node app.js remove command and our
flag "t":
node app.js remove -t="t"
We'll remove the note with the title t, and as shown previous, Note was removed
prints to the screen. And if I use the up arrow key twice, I can list the notes out
again, and you can see the note with the title t has indeed gone:
Let's remove one more note using the node app.js remove command. This time
we're going to use --title, which is the argument name, and the note we're going
to remove has the title flag title, as shown in this code:
When I remove it, it says Note was removed, and if I rerun the list command, I can
see that we have three notes left, the note was indeed removed , as shown here:
And that is it for the notes application.
Arrow functions
In this section, you're going to learn the ins and outs of the arrow function. It's an
ES6 feature, and we have taken a little look at it. Inside notes.js we used it in a
few basic examples to create methods such as fetchNotes and saveNotes, and we
also passed it into a few array methods like filter, and for each array, we used it
as the callback function that gets called once for every item in the array.
Now if you try to swap out all of the functions in a program with arrow
functions, it's most likely not going to work as expected because there are some
differences between the two, and it's really important to know what those
differences are, so you can make the decision to use a regular ES5 function or an
ES6 arrow function.
Using the arrow function
The goal in this section is to give you the knowledge to make that choice, and
we'll kick things off by creating a new file in the playground folder called arrow-
function.js:
Inside this file, we're going to play around with a few examples, going over
some of the subtleties to the arrow function. Before we type anything inside of
the file, I'll start up this file with nodemon, so every time we make a change it
automatically refreshes over in Terminal.
If you remember, nodemon is the utility we installed in Chapter 2, Node
Fundamentals - Part 1. It was a global npm module. The nodemon is the command
to run, and then we just pass in the file path like we would for any other Node
command. As we're going into the playground folder, and the file itself is called
arrow-function.js, we'll run the following command:
nodemon playground/arrow-function.js
We'll run the file, and nothing prints to the screen, as shown in the following
output, besides the nodemon logs because we have nothing in the file:
To get started, in the arrowfunction.js file, we'll create a function called square, by
making a variable called square and setting it equal to an arrow function.
To make our arrow function (=>), we'll first provide the arguments inside
parentheses. Since we'll be squaring a number, we just need one number, and I'll
refer to that number as x. If I pass in 3, I should expect 9 back, and if I pass in 9,
I would expect 81 back.
After the arguments list, we have to put the arrow in arrow function (=>) by
putting the equal sign and the greater than symbol together, creating our nice
little arrow. From here we can provide, inside curly braces, all the statements we
want to execute:
var square = (x) => {
};
Next, we might create a variable called result, setting that equal to x times x, then
we might return the result variable using the return keyword, as shown here:
var square = (x) => {
var result = x * x;
return result;
};
Now, obviously this can be done on one line, but the goal here is to illustrate that
when you use the statement arrow function (=>), you can put as many lines as
you want in between those curly braces. Let's call a square, we'll do that using
console.log so we can print the result to the screen. I'll call square; and we'll call
square with 9, the square of 9 would be 81, so we would expect 81 to print to the
screen:
var square = (x) => {
var result = x * x;
return result;
};
console.log(square(9));
I'll save the arrow function (=>) file, and in Terminal, 81 shows up just as we
expect:
Now the syntax we used in the previous example is the statement syntax for the
arrow function (=>). We've also explored the expression syntax earlier, which lets
you simplify your arrow functions when you return some expressions. In this
case all we need to do is specify the expression we want to return. In our case
that's x times x:
var square = (x) => x * x;
console.log(square(9));
You don't need to explicitly add the return keyword. When you use an arrow
function (=>) without your curly braces, it's implicitly provided for you. That
means we can save the function as shown previous and the exact same result is
going to print to the screen, 81 shows up.
This is one of the great advantages of arrow functions when you use them in
cases like filter or for those which we did in the notes.js file. It lets you simplify
your code keeping everything on one line and making your code a lot easier to
maintain and scan.
Now, there is one thing I want to note: when you have an arrow
function (=>) that has just one argument, you can actually leave off
the parentheses. If you have two or more arguments, or you have
zero arguments, you are going to need to provide the parentheses,
but if you just have one argument, you can reference it with no
parentheses.
If I save the file in this state, 81 still prints to the screen; and this is great we have
an even simpler version of our arrow function (=>):
Now that we have a basic example down, I want to move on to a more complex
example that's going to explore the nuances between regular functions and arrow
functions.
Exploring the difference between
regular and arrow functions
To illustrate the difference, I'll make a variable called user, which will be an
object. On this object we'll specify one property, name. Set name equal to the
string, your name, in this case I'll set it equal to the string Andrew:
var user = {
name: 'Andrew'
};
Then we can define a method on the user object. Right after name, with my
comma at the end of the line, I'll provide the method sayHi, setting it equal to an
arrow function (=>) that doesn't take any arguments. For the moment, we'll keep
the arrow function really simple:
var user = {
name: 'Andrew',
sayHi: () => {
}
};
All we'll do inside sayHi is use console.log to print to the screen, inside of template
strings Hi:
var user = {
name: 'Andrew',
sayHi: () => {
console.log(`Hi`);
}
};
We're not using template strings yet, but we will later so I'll use them here.
Down following the user object, we can test out sayHi by calling it, user.sayHi:
var user = {
name: 'Andrew',
sayHi: () => {
console.log(`Hi`);
}
};
user.sayHi();
I'll call it then save the file, and we would expect that Hi prints to the screen
because all our arrow function (=>) does is use console.log to print a static string.
Nothing in this case will cause any problems; you'd be able to swap out a regular
function for an arrow function (=>) without issue.
Now the first issue that will arise when you use arrow functions is the fact that
arrow functions do not bind a this keyword. So if you are using this inside your
function, it's not going to work when you swap it out for an arrow function (=>).
Now, this binding; refers to the parent binding, in our case there is no parent,
function so this would refer to the global this keyword. Now we have our
console.log that does not use this, I'll swap it out for a case that does.
We'll put a period after Hi, and I'll say I'm, followed by the name, which we
would usually be able to access via this.name:
var user = {
name: 'Andrew',
sayHi: () => {
console.log(`Hi. I'm ${this.name}`);
}
};
user.sayHi();
If I try to run this code, it is not going to work as expected; we're going to get Hi
I'm undefined printing to the screen, as shown here:
In order to fix this, we'll look at an alternative syntax to arrow functions that's
great when you're defining object literals, as we are in this case.
After sayHi, I'll make a new method called sayHiAlt using a different ES6 feature.
ES6 provides us a new way to make methods on objects; you provide the method
name, sayHiAlt, then you go right to the parentheses skipping the colon. There's
also no need for the function keyword, even though it is a regular function it's
not an arrow function (=>). Then we move on to our curly braces as shown here:
var user = {
name: 'Andrew',
sayHi: () => {
console.log(`Hi. I'm ${this.name}`);
},
sayHiAlt() {
}
};
user.sayHi();
Inside here I can have the exact same code we have in the sayHi function, but it is
going to work as expected. It's going to print Hi. I'm Andrew. I'll call sayHiAlt down
following instead of the regular sayHi method:
var user = {
name: 'Andrew',
sayHi: () => {
console.log(`Hi. I'm ${this.name}`);
},
sayHiAlt() {
console.log(`Hi. I'm ${this.name}`);
}
};
user.sayHiAlt();
And in Terminal, you can see Hi. I'm Andrew, prints to the screen:
The sayHiAlt syntax is a syntax that you can use to solve this problem when you
create functions on object literals. Now that we know that the this keyword does
not get bound, let's explore one other quirk that arrow functions have, it also
does not bind the arguments array.
Exploring the arguments array
Regular functions, like sayHiAlt, are going to have an arguments array that's
accessible inside of the function:
var user = {
name: 'Andrew',
sayHi: () => {
console.log(`Hi. I'm ${this.name}`);
},
sayHiAlt() {
console.log(arguments);
console.log(`Hi. I'm ${this.name}`);
}
};
user.sayHiAlt();
Now, it's not an actual array, it's more like an object with array; like properties,
but the arguments object is indeed specified in a regular function. If I pass in
one, two, and three and save the file, we'll get that back when we log out
arguments:
var user = {
name: 'Andrew',
sayHi: () => {
console.log(`Hi. I'm ${this.name}`);
},
sayHiAlt() {
console.log(arguments);
console.log(`Hi. I'm ${this.name}`);
}
};
user.sayHiAlt(1, 2, 3);
Inside nodemon, it's taking a quick second to restart, and right here we have our
object:
We have one, two, and three, we have the index for each as the property name,
and this works because we're using a regular function. If we were to switch to
the arrow function (=>) though, it is not going to work as expected.
I'll add console.log(arguments) inside of my arrow function (=>), and I'll switch from
calling sayHiAlt back to the original method sayHi, as shown here:
var user = {
name: 'Andrew',
sayHi: () => {
console.log(arguments);
console.log(`Hi. I'm ${this.name}`);
},
sayHiAlt() {
console.log(arguments);
console.log(`Hi. I'm ${this.name}`);
}
};
user.sayHi(1, 2, 3);
When I save the file in arrow-function.js, we'll get something a lot different from
what we had before. What we'll actually get is the global arguments variable,
which is the arguments variable for that wrapper function we explored: